package cn.kivensoft.util;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

/** 字符串增强处理类, 提供众多的字符串处理辅助函数
 * @author kiven lee
 * @version 1.0
 * @since 2020-01-05
 */
final public class Strings {
	public enum Base64Type {Standard, Url }
	public static final String UTF8 = "UTF-8";

	/** 本地时区相对于GMT时区的偏移值(单位: 毫秒) */
	public static final int LOCAL_ZONE_OFFSET = TimeZone.getDefault().getRawOffset();
	public static final TimeZone UTC = TimeZone.getTimeZone("UTC");

	// HEX编码相关变量
	public static final String HEX = "0123456789abcdef";
	// base64编解码相关变量
	private static final char[] BASE64_DIGEST = new char[] {
			'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
			'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
			'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
			'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
	};
	private static final char[] BASE64_DIGEST_URL = new char[] {
			'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
			'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
			'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
			'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
	};
	private static final char PAD = '=';
	private static final int MIMELINEMAX = 76;
	private static byte[] INV = null, INV_URL = null;
	private static final ReentrantLock invLock = new ReentrantLock();

	// 日期解析相关变量
	private static final ThreadLocal<Calendar> calThreadLocal =
			ThreadLocal.withInitial(Calendar::getInstance);

	private static final int SPLIT_DATE_CAPACITY = 8; //解析字符串允许的数组容量

	// 格式化时间/日期相关全局静态变量
	public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
	public static final String yyyy_MM_dd = "yyyy-MM-dd";
	public static final String HH_mm_ss = "HH:mm:ss";
	public static final String GMT_FMT = "yyyy-MM-dd'T'HH:mm:ss'Z'";

	private static final ThreadLocal<DateFormat> dtfThreadLocal =
			ThreadLocal.withInitial(() -> new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss));
	private static final ThreadLocal<DateFormat> dfThreadLocal =
			ThreadLocal.withInitial(() -> new SimpleDateFormat(yyyy_MM_dd));
	private static final ThreadLocal<DateFormat> tfThreadLocal =
			ThreadLocal.withInitial(() -> new SimpleDateFormat(HH_mm_ss));
	private static final ThreadLocal<DateFormat> gfThreadLocal =
			ThreadLocal.withInitial(() -> {
		SimpleDateFormat sdf = new SimpleDateFormat(GMT_FMT);
		sdf.setTimeZone(UTC);
		return sdf;
	});

	private static final ThreadLocal<DateTimeFormatter> dateTimeFmt =
			ThreadLocal.withInitial( () -> DateTimeFormatter.ofPattern(yyyy_MM_dd_HH_mm_ss));
	private static final ThreadLocal<DateTimeFormatter> dateFmt =
			ThreadLocal.withInitial( () -> DateTimeFormatter.ofPattern(yyyy_MM_dd));
	private static final ThreadLocal<DateTimeFormatter> timeFmt =
			ThreadLocal.withInitial( () -> DateTimeFormatter.ofPattern(HH_mm_ss));

	@FunctionalInterface
	public interface OnChars {
		int apply(char[] chars, int count);
	}

	@FunctionalInterface
	public interface OnBytes {
		int apply(byte[] bytes, int count);
	}

	@FunctionalInterface
	public interface OnReadChars {
		int apply(char[] chars, int off);
	}

	@FunctionalInterface
	public interface OnReadBytes {
		int apply(byte[] bytes, int off);
	}

	private Strings() {}

	/** null或空字符串返回缺省值
	 * @param value 要判断的字符串
	 * @return 参数为null或空字符串时返回缺省值, 否则返回原字符串
	 */
	public static String mapEmpty(String value, String def) {
		return (value == null || value.isEmpty()) ? def : value;
	}

	/** null或空字符串返回缺省值
	 * @param value 要判断的字符串
	 * @return 参数为null或空字符串时返回缺省值, 否则返回原字符串
	 */
	public static String mapEmpty(String value, Supplier<String> def) {
		return (value == null || value.isEmpty()) ? def.get() : value;
	}

	/** null/空字符串/空白字符串返回缺省值
	 * @param value 要判断的字符串
	 * @return 参数为null或空字符串时返回缺省值, 否则返回原字符串
	 */
	public static String mapBlank(String value, String def) {
		return !isBlank(value) ? value : def;
	}

	/** null/空字符/空白字符串串返回缺省值
	 * @param value 要判断的字符串
	 * @return 参数为null或空字符串时返回缺省值, 否则返回原字符串
	 */
	public static String mapBlank(String value, Supplier<String> def) {
		return (!isBlank(value)) ? value : def.get();
	}

	/** 字符串非空判断,空值和空字符串都返回true
	 * @param value 字符串
	 * @return 判断结果
	 */
	public static boolean isEmpty(String value) {
		return value == null || value.isEmpty();
	}

	/** 字符串判断
	 * @param values 多个变量
	 * @return 任意一个不为null且不为空, 返回false, 否则, 返回true
	 */
	public static boolean isEmpty(String... values) {
		for (String val : values)
			if (val != null && !val.isEmpty())
				return false;
		return true;
	}

	/** 字符串判断
	 * @param values 多个变量
	 * @return 任意一个为null或为空, 返回false, 否则, 返回true
	 */
	public static boolean isNotEmpty(String... values) {
		for (String val : values)
			if (val == null || val.isEmpty())
				return false;
		return true;
	}

	/** 字符串非空判断,空值和空字符串及空白字符串都返回true
	 * @param value 字符串
	 * @return 判断结果
	 */
	public static boolean isBlank(String value) {
		if (value == null || value.isEmpty())
			return true;
		for (int i = 0, len = value.length(); i < len; i++) {
			if (!Character.isWhitespace(value.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/** 字符串判断
	 * @param values 多个变量
	 * @return true: 所有字符串都是空白，false: 存在非空白字符串
	 */
	public static boolean isBlank(String... values) {
		for (String val : values)
			if (!isBlank(val))
				return false;
		return true;
	}

	/** 字符串判断
	 * @param values 多个变量
	 * @return true: 所有字符串都是非空白字符串, false: 存在空白字符串
	 */
	public static boolean isNotBlank(String... values) {
		for (String val : values)
			if (isBlank(val))
				return false;
		return true;
	}

	/** 右对齐字符串，长度小于minLength则在左边加上padChar
	 * @param src 源字符串
	 * @param minLength 最小长度
	 * @param padChar 补齐字符
	 * @return 生成的字符串
	 */
	public static String padLeft(String src, int minLength, char padChar) {
		int len = src == null ? 0 : src.length();
		if (len >= minLength) return src;
		char[] chars = new char[minLength];
		int padc = minLength - len;
		for(int i = 0; i < padc; ++i) chars[i] = padChar;
		if (len != 0) src.getChars(0, len, chars, padc);
		return new String(chars);
	}

	/** 左对齐字符串，长度小于minLength则在右边加上padChar
	 * @param src 源字符串
	 * @param minLength 最小长度
	 * @param padChar 补齐字符
	 * @return 生成的字符串
	 */
	public static String padRight(String src, int minLength, char padChar) {
		int len = src == null ? 0 : src.length();
		if (len >= minLength) return src;
		char[] chars = new char[minLength];
		if (len != 0) src.getChars(0, len, chars, 0);
		for(int i = len; i < minLength; ++i) chars[i] = padChar;
		return new String(chars);
	}

	/** 中间对齐字符串, 长度小于minLength往两头扩展padChar
	 * @param src 要对齐的字符串
	 * @param minLength 最小长度
	 * @param padChar 扩展字符
	 * @return String
	 */
	public static String padString(String src, int minLength, char padChar) {
		int len = src == null ? 0 : src.length();
		if (len >= minLength) return src;
		char[] chars = new char[minLength];
		int pos = (minLength - len) >> 1;
		for (int i = 0; i < pos; ++i) chars[i] = padChar;
		if (len != 0) src.getChars(0, len, chars, pos);
		for(pos += len; pos < minLength; ++pos) chars[pos] = padChar;
		return new String(chars);
	}

	/** 路径连接, 去除中间多余的路径分隔符'/'或'\'
	 * @param paths 要连接的多个路径字符串
	 * @return 生成的路径
	 */
	public static String joinPath(String... paths) {
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for (String p : paths) {
			// 无效路径, 忽略, 进入下一次循环
			if (p == null || p.isEmpty()) continue;
			// 第一次添加, 忽略对前导路径分隔符的判断
			if (first) {
				first = false;
				sb.append(p);
				continue;
			}
			int sb_len = sb.length();
			char p_char = p.charAt(0), sb_char = sb.charAt(sb_len - 1);
			boolean b1 = p_char == '/' || p_char == '\\';
			boolean b2 = sb_char == '/' || sb_char == '\\';
			// 缓冲区末尾及要添加的路径均有分隔符, 删除缓冲区末尾的分隔符
			if (b2 && b1) sb.setLength(sb_len - 1);
				// 缓冲区末尾及要添加的路径均没有分隔符, 需要添加一个分隔符到缓冲区末尾
			else if (!b2 && !b1) sb.append('/');
			// 分隔符处理完毕, 添加路径到缓冲区中
			sb.append(p);
		}
		return sb.toString();
	}

	/** 生成重复N次的字符串
	 * @param str 要重复的字符串
	 * @param count 重复次数
	 * @return String
	 */
	public static String repeat(String str, int count) {
		if (str == null || count == 0) return null;
		int strlen = str.length();
		//如果字符串长度为1，转为调用速度更快的方法
		if(strlen == 1) return repeat(str.charAt(0), count);

		char[] retChars = new char[strlen * count];
		for(int i = 0; i < count; ++i)
			str.getChars(0, strlen, retChars, i * strlen);
		return new String(retChars);
	}

	/** 生成重复N次的字符串
	 * @param ch 要重复的字符
	 * @param count 重复次数
	 * @return String
	 */
	public static String repeat(char ch, int count) {
		char[] chars = new char[count];
		Arrays.fill(chars, ch);
		return new String(chars);
	}

	/** 转换成16进制
	 * @param bytes 要转换的字节数组
	 * @return String
	 */
	public static String toHex(byte[] bytes) {
		return toHex(bytes, null, null);
	}

	/** 转换成16进制
	 * @param bytes 要转换的字节数组
	 * @param separator 分隔符, 可以为null
	 * @return String
	 */
	public static String toHex(byte[] bytes, String separator) {
		return toHex(bytes, separator, null);
	}

	/** 转换成16进制
	 * @param bytes 要转换的字节数组
	 * @param separator 分隔符, 可以为null
	 * @param prefix 前缀, 可以为null
	 * @return String
	 */
	public static String toHex(byte[] bytes, String separator, String prefix) {
		if(bytes == null) return null;
		final int blen = bytes.length;
		if (blen == 0) return "";

		StringBuilder sb = new StringBuilder();
		hexEncode(bytes, separator, prefix, (chs, len) -> {
			sb.append(chs, 0, len);
			return len;
		});
		return sb.toString();
	}

	/** 从16进制转换成字符串
	 * @param str 源
	 * @return byte[]
	 */
	public static byte[] fromHex(String str) {
		return fromHex(str, 0, null);
	}

	/** 从16进制转换成字符串
	 * @param str 源
	 * @param separator 分隔符
	 * @return byte[]
	 */
	public static byte[] fromHex(String str, String separator) {
		return fromHex(str, 0, separator);
	}

	/** 从16进制转换成字符串
	 * @param str 源
	 * @param off str起始位置
	 * @param separator 分隔符
	 * @return byte[]
	 */
	public static byte[] fromHex(String str, int off, String separator) {
		if (str == null || str.length() - off < 2) return null;

		int sep_len = separator == null ? 0 : separator.length();
		int step = sep_len + 2;
		int count = (str.length() - off + sep_len) / step;
		byte[] ret = new byte[count];
		for(int i = 0, j = off, jmax = str.length() - 1; j < jmax; j += step)
			ret[i++] = (byte) (c2b(str.charAt(j)) << 4 | c2b(str.charAt(j + 1)));

		return ret;
	}

	/** 转换成16进制
	 * @param bytes 要转换的字节数组
	 * @param separator 分隔符, 可以为null
	 * @param prefix 前缀, 可以为null
	 */
	public static void hexEncode(byte[] bytes, String separator, String prefix, OnChars func) {
		if(bytes == null) return;
		final int blen = bytes.length;
		if (blen == 0) return;

		//转码后的长度是字节数组长度的2倍
		final int READ_COUNT = 64;
		final int sep_len = separator == null ? 0 : separator.length();
		final int pre_len = prefix == null ? 0 : prefix.length();
		char[] chars = new char[(2 + sep_len) * READ_COUNT];
		boolean first = true;

		// 写入前缀
		if (prefix != null) {
			char[] cs = new char[pre_len];
			prefix.getChars(0, pre_len, cs, 0);
			if (func.apply(cs, pre_len) == 0)
				return;
		}

		// 针对分隔符为0或者为1个字符时的优化实现
		int sidx = 0;
		if (sep_len == 0) {
			// 分段生成，按缓冲区大小
			for (int c = 0, m = blen / READ_COUNT; c < m; ++c) {
				// 一次写入64个字节对应的十六进制字符串
				for(int i = 0; i < (READ_COUNT << 1); i += 2) {
					int b = bytes[sidx++];
					chars[i] = HEX.charAt(b >>> 4 & 0xf);
					chars[i + 1] = HEX.charAt(b & 0xf);
				}
				if (func.apply(chars, READ_COUNT << 1) == 0)
					return;
			}
		} else if (sep_len == 1) {
			char sep = separator.charAt(0);

			for (int c = 0, m = blen / READ_COUNT; c < m; ++c) {
				int widx = 0;
				for (int i = 0; i < READ_COUNT; ++i) {
					if (first) first = false;
					else chars[widx++] = sep;

					int b = bytes[sidx++];
					chars[widx++] = HEX.charAt(b >>> 4 & 0xf);
					chars[widx++] = HEX.charAt(b & 0xf);
				}
				if (func.apply(chars, widx) == 0)
					return;
			}
		} else {
			for (int c = 0, m = blen / READ_COUNT; c < m; ++c) {
				int widx = 0;
				for (int i = 0; i < READ_COUNT; ++i) {
					if (first) first = false;
					else {
						separator.getChars(0, sep_len, chars, widx);
						widx += sep_len;
					}

					int b = bytes[sidx++];
					chars[widx++] = HEX.charAt(b >>> 4 & 0xf);
					chars[widx++] = HEX.charAt(b & 0xf);
				}
				if (func.apply(chars, widx) == 0)
					return;
			}
		}

		// 处理剩余不足64字节的数据
		int surplen = blen % READ_COUNT;
		if (surplen > 0) {
			int widx = 0;
			for(int i = 0; i < surplen; ++i) {
				if (sep_len > 0) {
					if (first) first = false;
					else {
						separator.getChars(0, sep_len, chars, widx);
						widx += sep_len;
					}
				}
				int b = bytes[sidx++];
				chars[widx++] = HEX.charAt(b >>> 4 & 0xf);
				chars[widx++] = HEX.charAt(b & 0xf);
			}
			func.apply(chars, widx);
		}
	}

	/** 从16进制转换成字符串
	 * @param str 源
	 * @param off str起始位置
	 * @param separator 分隔符
	 * @param func 回调函数
	 */
	public static void hexDecode(String str, int off, String separator, OnBytes func) {
		if (str == null || str.length() - off < 2) return;

		final int READ_COUNT = 128;
		int sep_len = separator == null ? 0 : separator.length();
		int step = sep_len + 2;
		byte[] ret = new byte[READ_COUNT];
		int idx = 0;
		for(int i = off, imax = str.length() - 1; i < imax; i += step) {
			ret[idx++] = (byte) (c2b(str.charAt(i)) << 4 | c2b(str.charAt(i + 1)));
			if (idx >= READ_COUNT) {
				if (func.apply(ret, idx) == 0)
					return;
				idx = 0;
			}
		}

		if (idx >= 0)
			func.apply(ret, idx);
	}

	// 16进制表示的字符转成二进制表示
	private static byte c2b(char c) {
		if ((c & 64) != 0) c += 9;
		return (byte) (c & 15);
	}

	/** 字符转utf8字节
	 * @param src 字符
	 * @param dst utf8字节数组
	 * @param pos 开始写入位置
	 * @return 本次写入的字节数
	 */
	public static int utf32ToUtf8(int src, byte[] dst, int pos) {
		// ascii字符, 只有1个
		if (src < 0x80) {
			dst[pos] = (byte) src;
			return 1;
		} else if (src < 0x800) {
			dst[pos++] = (byte) (0b1100_0000 | ((src >> 6) & 0b0001_1111));
			dst[pos] = (byte) (0b1000_0000 | (src & 0b0011_1111));
			return 2;
		} else if (src < 0x1_0000) {
			dst[pos++] = (byte) (0b1110_0000 | ((src >> 12) & 0b0000_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 6) & 0b0011_1111));
			dst[pos] = (byte) (0b1000_0000 | (src & 0b0011_1111));
			return 3;
		} else if (src < 0x11_0000){
			dst[pos++] = (byte) (0b1111_0000 | ((src >> 18) & 0b0000_0111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 12) & 0b0011_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 6) & 0b0011_1111));
			dst[pos] = (byte) (0b1000_0000 | (src & 0b0011_1111));
			return 4;
		} else if (src < 0x0400_0000) {
			dst[pos++] = (byte) (0b1111_1000 | ((src >> 24) & 0b0000_0011));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 18) & 0b0011_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 12) & 0b0011_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 6) & 0b0011_1111));
			dst[pos] = (byte) (0b1000_0000 | (src & 0b0011_1111));
			return 5;
		} else {
			dst[pos++] = (byte) (0b1111_1100 | ((src >> 30) & 0b0000_0001));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 24) & 0b0011_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 18) & 0b0011_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 12) & 0b0011_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((src >> 6) & 0b0011_1111));
			dst[pos] = (byte) (0b1000_0000 | (src & 0b0011_1111));
			return 6;
		}
	}

	/** 判断java的char类型是否是扩展编码（即需要2个char来编码的unicode字符）
	 * @param c 要判断的字符
	 * @return true: 需要更多的字符, false: 不需要
	 */
	public static boolean hasMoreChar(char c) {
		return c >= 0xD800 && c <= 0xDFFF;
	}

	/** utf16多字节编码转utf32
	 * @param c1 第一个字符
	 * @param c2 第二个字符
	 * @return utf32编码
	 */
	public static int utf16To32(char c1, char c2) {
		return 0x1_0000 + ((c1 & 0x3FF) << 10) | (c2 & 0x3FF);
	}

	/** 单个char转成utf8编码的字节数组
	 * @param c 要转换的字符
	 * @param dst 目标字节数组(用户自行确保剩余长度足以写入，函数中不做校验)
	 * @param pos 写入的起始位置
	 * @return 写入长度, 写入长度等于0，表示这是个代理字符
	 */
	public static int charToUtf8(char c, char prev, byte[] dst, int pos) {
		if (c < 0x80) {
			dst[pos] = (byte) c;
			return 1;
		} else if (c < 0x800) {
			dst[pos++] = (byte) (0b1100_0000 | ((c >> 6) & 0b0001_1111));
			dst[pos] =(byte) (0b1000_0000 | (c & 0b0011_1111));
			return 2;
		} else if (c < 0xD800 || c > 0xDFFF) {
			dst[pos++] = (byte) (0b1110_0000 | ((c >> 12) & 0b0000_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((c >> 6) & 0b0011_1111));
			dst[pos] = (byte) (0b1000_0000 | (c & 0b0011_1111));
			return 3;
		} else {
			if (prev == 0) return 0;
			int n = 0x1_0000 + ((prev & 0x3FF) << 10) | (c & 0x3FF);
			dst[pos++] = (byte) (0b1111_0000 | ((n >> 18) & 0b0000_0111));
			dst[pos++] = (byte) (0b1000_0000 | ((n >> 12) & 0b0011_1111));
			dst[pos++] = (byte) (0b1000_0000 | ((n >> 6) & 0b0011_1111));
			dst[pos] = (byte) (0b1000_0000 | (n & 0b0011_1111));
			return 4;
		}
	}

	/** 返回字符串的utf8格式字节大小, 只计算, 不分配任何空间
	 * @param src 源字符串
	 * @return 转成utf8所需字节数组大小
	 */
	public static int utf8Count(CharSequence src) {
		if (src == null || src.length() == 0) return 0;
		return utf8Count(src, 0, src.length());
	}

	/** 返回字符串的utf8格式字节大小, 只计算, 不分配任何空间
	 * @param src 源字符串
	 * @param begin 源字符串起始位置
	 * @param end 源字符串结束位置
	 * @return 转成utf8所需字节数组大小
	 */
	public static int utf8Count(CharSequence src, int begin, int end) {
		int count = 0;
		if (src != null && src.length() > 0) {
			for (int i = begin; i < end; ++i) {
				char c = src.charAt(i);
				if (c >= 0xD800 && c <= 0xDFFF) {
					count += 4;
					++i;
				} else
					count += c < 0x80 ? 1 : c < 0x800 ? 2 : 3;
			}
		}
		return count;
	}

	/** 计算utf8字节数组转码成字符串所需要的长度, 纯计算, 不进行解码
	 * @param src 源utf8字节数组
	 * @return 转码所需的字符串长度
	 */
	public static int charCount(byte[] src) {
		return src == null ? 0 : charCount(src, 0, src.length);
	}

	/** 计算utf8字节数组转码成字符串所需要的长度, 纯计算, 不进行解码
	 * @param src 源utf8字节数组
	 * @param srcBegin 源起始位置
	 * @param srcEnd 源结束位置
	 * @return 转码所需的字符串长度
	 */
	public static int charCount(byte[] src, int srcBegin, int srcEnd) {
		if (src == null || src.length == 0) return 0;
		int count = 0;
		while (srcBegin < srcEnd) {
			int b = src[srcBegin] & 0xFF;
			srcBegin += b < 0x80 ? 1 : b < 0xE0 ? 2 : b < 0xF0 ? 3 : 4;
			++count;
			if (b >= 0xF0) ++count;
		}
		return count;
	}

	/** utf8解码
	 * @param src utf8格式字节数组
	 * @param srcBegin 源起始位置
	 * @param srcEnd 源结束位置
	 * @param func 回调函数, 每次转码一个字符后回调
	 */
	@SuppressWarnings("UnusedReturnValue")
	public static void utf8Decode(byte[] src, int srcBegin, int srcEnd, OnChars func) {
		final int BUF_SIZE = 256;
		if (src == null || srcBegin >= srcEnd) return;
		char[] buf = new char[BUF_SIZE];
		int pos = 0;

		while (srcBegin < srcEnd) {
			if (pos >= BUF_SIZE - 1) {
				if (func.apply(buf, pos) == 0)
					return;
				pos = 0;
			}

			int b = src[srcBegin++] & 0xFF;

			// ascii字符
			if (b < 0x80) {
				buf[pos++] = (char) b;
				break;
			}

			if (b < 0b1100_0000 || b >= 0b1111_1000)
				throw new RuntimeException("bad byte to transaction utf8");

			if (b < 0b1110_0000) {
				if (srcBegin >= srcEnd)
					throw new RuntimeException("bad byte to transaction utf8");
				buf[pos++] = (char) ((b & 0b0001_1111) << 6 | (src[srcBegin++] & 0b0011_1111));
			} else if (b < 0b1111_0000) {
				if (srcBegin + 1 >= srcEnd)
					throw new RuntimeException("bad byte to transaction utf8");
				buf[pos++] = (char) ((b & 0b0000_1111) << 12 | (src[srcBegin++] & 0b0011_1111) << 6
						| (src[srcBegin++] & 0b0011_1111));
			} else {
				if (srcBegin + 2 >= srcEnd)
					throw new RuntimeException("bad byte to transaction utf8");
				int n = ((b & 0b0000_0111) << 18 | (src[srcBegin++] & 0b0011_1111) << 12
						| (src[srcBegin++] & 0b0011_1111) << 6 | (src[srcBegin++] & 0b0011_1111)) - 0x1_0000;
				buf[pos++] = (char) (0xD800 + (n >> 10));
				buf[pos++] = (char) (0xDC00 + (n & 0x3FF));
			}
		}

		if (pos > 0)
			func.apply(buf, pos);
	}

	/** UTF8编码
	 * @param src 源字符串
	 * @param srcBegin 源字符串起始位置
	 * @param srcEnd 源字符串结束位置
	 * @param func 回调函数, 每次转码一个字符后回调
	 */
	public static void utf8Encode(CharSequence src, int srcBegin, int srcEnd, OnBytes func) {
		final int BUF_SIZE = 256;
		if (src == null) return;
		byte[] buf = new byte[BUF_SIZE];
		int pos = 0;
		char prev = 0;
		for (int i = srcBegin; i < srcEnd; ++i) {
			if (pos >= BUF_SIZE - 3) {
				if (func.apply(buf, pos) == 0)
					return;
				pos = 0;
			}

			char c = src.charAt(i);
			int n = charToUtf8(c, prev, buf, pos);
			pos += n;
			if (n == 0) prev = c;
		}

		if (pos > 0) func.apply(buf, pos);
	}

	/** utf8字节数组转字符串
	 * @param bytes utf8格式的字节数组
	 * @return String
	 */
	public static String fromBytes(byte[] bytes) {
		return fromBytes(bytes, 0, bytes.length);
	}

	/** utf8字节数组转字符串
	 * @param bytes utf8格式的字节数组
	 * @param begin 开始位置
	 * @return String
	 */
	public static String fromBytes(byte[] bytes, int begin) {
		return fromBytes(bytes, 0, bytes.length);
	}

	/** 字节数组转字符串
	 * @param bytes utf8格式的字节数组
	 * @param begin 开始位置
	 * @param end 结束位置
	 * @return String
	 */
	public static String fromBytes(byte[] bytes, int begin, int end) {
		return new String(bytes, begin, end - begin, StandardCharsets.UTF_8);
	}

	/** 字符串转utf8字节数组
	 * @param string 源
	 * @return byte[]
	 */
	public static byte[] toBytes(CharSequence string) {
		return toBytes(string, 0, string.length());
	}

	/** String 转 utf8
	 * @param src 源字符串
	 * @param srcBegin 源字符串起始位置
	 * @param srcEnd 源字符串结束位置
	 * @return utf8字节数组
	 */
	public static byte[] toBytes(CharSequence src, int srcBegin, int srcEnd) {
		if (src == null) return null;
		byte[] bytes = new byte[utf8Count(src, srcBegin, srcEnd)];
		int[] pos = { 0 };
		utf8Encode(src, srcBegin, srcEnd, (chs, len) -> {
			System.arraycopy(chs, 0, bytes, pos[0], len);
			pos[0] += len;
			return len;
		});
		return bytes;
	}

	/** md5加密
	 * @param bytes 要加密的内容
	 * @return 加密后的内容
	 */
	public static byte[] md5(byte[] bytes) {
		try {
			MessageDigest md = (MessageDigest) MessageDigest.getInstance("MD5").clone();
			return md.digest(bytes);
		} catch (Exception e) {
			return null;
		}
	}

	/** hmacsha1加密
	 * @param key 密钥
	 * @param bytes 要加密的内容
	 * @return 加密后的内容
	 */
	public static byte[] hmacsha1(byte[] key, byte[] bytes) {
		try {
			SecretKey secretKey = new SecretKeySpec(key, "HmacSHA1");
			Mac mac = (Mac) Mac.getInstance("HmacSHA1").clone();
			mac.init(secretKey);
			return mac.doFinal(bytes);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// 初始化base64解码索引素组全局变量
	private static byte[] initInv(Base64Type type) {
		char[] digests;

		if (type == Base64Type.Standard) {
			if (INV != null)
				return INV;
			else
				digests = BASE64_DIGEST;

		} else {
			if (INV_URL != null)
				return INV_URL;
			else
				digests = BASE64_DIGEST_URL;
		}

		byte[] inv = new byte[128];
		Arrays.fill(inv, (byte) -1);
		for (int i = 0, n = digests.length; i < n; ++i)
			inv[digests[i]] = (byte) i;
		inv[PAD] = 0;

		invLock.lock();
		if (type == Base64Type.Standard) {
			if (INV == null)
				INV = inv;
		} else {
			if (INV_URL == null)
				INV_URL = inv;
		}
		invLock.unlock();

		return inv;
	}

	/**
	 * base64 编码
	 * @param bytes 要编码的内容
	 * @return 编码后的base64字符串
	 */
	public static String toBase64(byte[] bytes) {
		return toBase64(Base64Type.Standard, bytes, true, false);
	}

	/**
	 * base64 编码
	 * @param bytes 要编码的内容
	 * @param padding 是否自动补齐
	 * @return 编码后的base64字符串
	 */
	public static String toBase64(byte[] bytes, boolean padding) {
		return toBase64(Base64Type.Standard, bytes, padding, false);
	}

	/**
	 * base64 URL 编码
	 * @param bytes 要编码的内容
	 * @return 编码后的base64字符串
	 */
	public static String toBase64Url(byte[] bytes) {
		return toBase64(Base64Type.Url, bytes, true, false);
	}

	/**
	 * base64 URL 编码
	 * @param bytes 要编码的内容
	 * @param padding 是否自动补齐
	 * @return 编码后的base64字符串
	 */
	public static String toBase64Url(byte[] bytes, boolean padding) {
		return toBase64(Base64Type.Url, bytes, padding, false);
	}

	/**
	 * base64 MIME 编码
	 * @param bytes 要编码的内容
	 * @return 编码后的base64字符串
	 */
	public static String toBase64Mime(byte[] bytes) {
		return toBase64(Base64Type.Standard, bytes, true, true);
	}

	/**
	 * base64 编码
	 * @param type 编码类型
	 * @param bytes 编码内容
	 * @param padding 是否自动补齐
	 * @param multiLine 是否自动换行
	 * @return 编码后的base64字符串
	 */
	public static String toBase64(Base64Type type, byte[] bytes, boolean padding, boolean multiLine) {
		int[] pos = {0};
		StringBuilder sb = new StringBuilder((bytes.length + 2) / 3 * 4);

		base64Encode(type, padding, multiLine, (bs, off) -> {
			int len1 = bs.length - off, len2 = bytes.length - pos[0];
			int len = len1 <= len2 ? len1 : len2;
			if (len > 0)
				System.arraycopy(bytes, pos[0], bs, off, len);
			pos[0] += len;
			return len;
		}, (cs, len) -> {
			sb.append(cs, 0, len);
			return len;
		});

		return sb.toString();
	}

	/**
	 * base64 解码
	 * @param str 要解码的字符串
	 * @return 解码后的内容
	 */
	public static byte[] fromBase64(String str) {
		return fromBase64(Base64Type.Standard, str);
	}

	/**
	 * base64 URL 解码
	 * @param str 要解码的字符串
	 * @return 解码后的内容
	 */
	public static byte[] fromBase64Url(String str) {
		return fromBase64(Base64Type.Url, str);
	}

	/**
	 * base64 解码
	 * @param type 编码类型
	 * @param str 要解码的字符串
	 * @return 解码后的内容
	 */
	public static byte[] fromBase64(Base64Type type, String str) {
		int[] pos = {0, 0};
		byte[] bytes = new byte[lengthOfBase64Decode(str)];
		int strLen = str.length();

		Strings.base64Decode(type, (chs, off) -> {
			int len1 = strLen - pos[0], len2 = chs.length - off;
			int len = len1 <= len2 ? len1 : len2;
			if (len > 0)
				str.getChars(pos[0], pos[0] + len, chs, off);
			pos[0] += len;
			return len;
		}, (bs, len) -> {
			System.arraycopy(bs, 0, bytes, pos[1], len);
			pos[1] += len;
			return len;
		});

		return bytes;
	}

	/**
	 * 计算base64编码后需要的字符串长度
	 * @param bytes 编码内容
	 * @param padding 是否自动补齐
	 * @param multiLine 是否自动换行
	 * @return base64编码字符串长度
	 */
	public static int lengthOfBase64Encode(byte[] bytes, boolean padding, boolean multiLine) {
		int len = (bytes.length / 3) << 2;
		int mod = bytes.length % 3;
		if (mod > 0)
			len += padding ? 4 : mod == 1 ? 2 : 3;
		if (multiLine) {
			int m = len % 76;
			len += (len / 76) << 1;
			if (m == 0) len -= 2;
		}

		return len;
	}

	/**
	 * 计算base64解码后需要的字节长度
	 * @param str 要解码的字符串
	 * @return 解码后的字节长度
	 */
	public static int lengthOfBase64Decode(String str) {
		int strLen = str.length(), skip = 0;
		while (str.charAt(strLen - 1) == PAD)
			--strLen;

		for (int i = 0; i < strLen; ++i) {
			char c = str.charAt(i);
			if (c == '\r' || c == '\n')
				++skip;
		}
		int len = strLen - skip;
		int mod = len % 4;
		len = len / 4 * 3;
		if (mod > 0) len += mod == 2 ? 1 : 2;
		return len;
	}

	/**
	 * base64编码
	 * @param type 编码类别
	 * @param padding 是否自动补齐
	 * @param multiLine 是否自动换行
	 * @param read 读取函数
	 * @param write 写入函数
	 */
	public static void base64Encode(Base64Type type, boolean padding, boolean multiLine,
			OnReadBytes read, OnChars write) {

		char[] digest = type == Base64Type.Standard ? BASE64_DIGEST : BASE64_DIGEST_URL;

		final int BUF_SIZE = 76;
		char[] buf = new char[BUF_SIZE + 2];
		byte[] bbuf = new byte[BUF_SIZE / 4 * 3];
		int readPos = 0, writePos = 0, readCount;
		boolean nl = false;
		while ((readCount = read.apply(bbuf, readPos)) > 0) {
			if (nl) {
				nl = false;
				buf[writePos++] = '\r';
				buf[writePos++] = '\n';
			}

			for (int i = 0; i < readCount - 2; i += 3) {
				byte b1 = bbuf[i], b2 = bbuf[i + 1], b3 = bbuf[i + 2];
				buf[writePos++] = digest[(b1 >> 2) & 0b0011_1111];
				buf[writePos++] = digest[(b1 & 0b0000_0011) << 4 | ((b2 >>> 4) & 0b0000_1111)];
				buf[writePos++] = digest[(b2 & 0b0000_1111) << 2 | ((b3 >>> 6) & 0b0000_0011)];
				buf[writePos++] = digest[b3 & 0b0011_1111];
			}

			readPos = readCount % 3;
			switch (readPos) {
				case 1:
					bbuf[0] = bbuf[readCount - 1];
					break;
				case 2:
					bbuf[0] = bbuf[readCount - 2];
					bbuf[1] = bbuf[readCount - 1];
					break;
			}

			if (multiLine && readCount == BUF_SIZE / 4 * 3)
				nl = true;

			if (write.apply(buf, writePos) == 0)
				return;
			writePos = 0;
		}

		if (readPos > 0) {
			if (nl) {
				buf[writePos++] = '\r';
				buf[writePos++] = '\n';
			}

			byte b1 = bbuf[0];
			byte b2 = readPos == 1 ? 0 : bbuf[1];
			buf[writePos++] = digest[(b1 >> 2) & 0b0011_1111];
			buf[writePos++] = digest[(b1 & 0b0000_0011) << 4 | ((b2 >>> 4) & 0b0000_1111)];
			if (readPos == 2) {
				buf[writePos++] = digest[(b2 & 0b0000_1111) << 2];
				if (padding)
					buf[writePos++] = PAD;
			} else if (padding) {
				buf[writePos++] = PAD;
				buf[writePos++] = PAD;
			}

			write.apply(buf, writePos);
		}
	}

	/**
	 * base64解码，处理自动换行
	 * @param type 解码类别
	 * @param read 读取函数
	 * @param write 写入函数
	 */
	public static void base64Decode(Base64Type type, OnReadChars read, OnBytes write) {
		byte[] inv = type == Base64Type.Standard ? INV : INV_URL;
		// 第一次使用需要对INV进行初始化
		if (inv == null) inv = initInv(type);

		final int BUF_SIZE = 256;
		char[] buf = new char[BUF_SIZE];
		byte[] bbuf = new byte[BUF_SIZE / 4 * 3];
		int readPos = 0, writePos = 0, readCount;
		byte b1 = 0, b2 = 0, b3 = 0;
		while ((readCount = read.apply(buf, 0)) > 0) {
			while (readCount >= 0 && buf[readCount - 1] == '=')
				--readCount;

			for (int i = 0; i < readCount; ++i) {
				char c = buf[i];
				if (c == '\r' || c == '\n') continue;
				byte b = inv[c];
				switch (readPos++) {
					case 0: b1 = b; break;
					case 1: b2 = b; break;
					case 2: b3 = b; break;
					default:
						bbuf[writePos++] = (byte) (b1 << 2 | b2 >> 4);
						bbuf[writePos++] = (byte) (b2 << 4 | b3 >> 2);
						bbuf[writePos++] = (byte) (b3 << 6 | b);
						readPos = 0;
				}
			}

			switch (readPos) {
				case 1:
					buf[0] = buf[readCount - 1];
					break;
				case 2:
					buf[0] = buf[readCount - 2];
					buf[1] = buf[readCount - 1];
					break;
				case 3:
					buf[0] = buf[readCount - 3];
					buf[1] = buf[readCount - 2];
					buf[2] = buf[readCount - 1];
					break;
			}

			if (write.apply(bbuf, writePos) == 0)
				return;
			writePos = 0;
		}

		if (readPos > 0) {
			switch (readPos) {
				case 2:
					bbuf[0] = (byte) (b1 << 2 | b2 >> 4);
					break;
				case 3:
					bbuf[0] = (byte) (b1 << 2 | b2 >> 4);
					bbuf[1] = (byte) (b2 << 4 | b3 >> 2);
					break;
			}
			write.apply(bbuf, readPos - 1);
		}
	}

	/** 判断是否是纯数字,允许开头有加减号
	 * @param text 要判断的文本
	 * @return true:纯数字,false:不是纯数字
	 */
	public static boolean isInt(String text) {
		if (text == null || text.isEmpty()) return false;
		char c = text.charAt(0);
		if (c != '-' && c != '+' && (c < '0' || c > '9')) return false;
		for (int i = 1, n = text.length(); i < n; ++i) {
			c = text.charAt(i);
			if (c < '0' || c > '9') return false;
		}
		return true;
	}

	/** 判断字符串是否浮点数格式
	 * @param text 要判断的文本
	 * @return true:浮点数,false:不是浮点数
	 */
	public static boolean isNumber(String text) {
		if (text == null || text.isEmpty()) return false;

		int index = 0, len = text.length();
		char c = text.charAt(index++);

		if (c < '0' || c > '9') {
			if (c != '-' && c != '+') return false;
			if (len < 2) return false;
			c = text.charAt(index++);
			if (c < '0' || c > '9') return false;
		}

		for (boolean findedDot = false; index < len; ++index) {
			c = text.charAt(index);
			if (c < '0' || c > '9') {
				if (c == '.') {
					if (findedDot || index == len - 1) return false;
					else findedDot = true;
				}
				else return false;
			}
		}

		return true;
		//return Pattern.matches("-?[0-9]+(\\.[0-9]+)?", text);
	}

	/** 判断是否金额格式
	 * @param text 要判断的文本
	 * @return true:金额格式,false:不是金额格式
	 */
	public static boolean isMoney(String text) {
		if (text == null || text.isEmpty()) return false;

		boolean hasDot = false;
		int lastNumber = 0;
		for (int i = 0, len = text.length(); i < len; ++i) {
			char c = text.charAt(i);
			if (c >= '0' && c <= '9') {
				if (hasDot && ++lastNumber > 2) return false;
			}
			else if (c == '.') {
				if (hasDot || i == 0 || i == len - 1) return false;
				else hasDot = true;
			} else {
				if (i != 0 || (c != '-' && c != '+')) {
					return false;
				}
			}
		}

		return true;
		//return Pattern.matches("-?[0-9]+(\\.[0-9][0-9]?)?", text);
	}

	/** 判断是否是手机号码 */
	public static boolean isMobile(String text) {
		if (text == null || text.length() != 11) return false;
		if (text.charAt(0) != '1') return false;
		char c2 = text.charAt(1);
		if (c2 < '3' || c2 > '9') return false;
		for (int i = 2, imax = text.length(); i < imax; ++i) {
			char c3 = text.charAt(i);
			if (c3 < '0' || c3 > '9') return false;
		}
		return true;
	}

	/** 判断是否email地址 */
	public static boolean isEmail(String text) {
		if (text == null || text.length() < 5) return false;
		int len = text.length(), at = -1, dot = -1;
		for (int i = 0; i < len; ++i) {
			char c = text.charAt(i);
			if (c == '@') {
				if (at != -1 || i == 0) return false;
				at = i;
			} else if (c == '.') {
				if (dot != -1 || i < 3) return false;
				dot = i;
			}
		}

		return at > 0 && at < len - 3 && dot >= 3 && dot < len - 1;
	}

	/** 判断是否都是ascii字符 */
	public static boolean isAscii(String text) {
		if (text == null || text.isEmpty()) return false;
		for (int i = 0, len = text.length(); i < len; ++i) {
			char c = text.charAt(i);
			if (c < 32 || c > 127) return false;
		}
		return true;
	}

	/** 求取文本串长度, 英文字符算1个, 汉字算2个 */
	public static int hzlen(String text) {
		if (text == null || text.isEmpty()) return 0;
		int count = 0;
		for (int i = 0, len = text.length(); i < len; ++i) {
			char c = text.charAt(i);
			count += (c < 0x4e00 || c > 0x9fa5) ? 2 : 1;
		}
		return count;
	}

	public enum CharType { 数字, 小写字母, 大写字母, 特殊符号, 非ASCII字符 }

	/** 密码强壮级别
	 * @return 包含类型的枚举集合
	 */
	public static EnumSet<CharType> checkCharTypes(String value) {
		EnumSet<CharType> ret = EnumSet.noneOf(CharType.class);

		if (value == null || value.isEmpty()) return ret;

		int flag = 0;
		for (int i = 0, imax = value.length(); i < imax; ++i) {
			if (flag == 0b11111) break;
			char c = value.charAt(i);
			if (c >= '0' && c <= '9') flag |= 0b1;
			else if (c >= 'a' && c <= 'z') flag |= 0b10;
			else if (c >= 'A' && c <= 'Z') flag |= 0b100;
			else if (c >= 0x21 && c <= 0x2f
					|| c >= 0x3a && c <= 0x40
					|| c >= 0x5b && c <= 0x60
					|| c >= 0x7b && c <= 0x7e)
				flag |= 0b1000;
			else if (c > 127) flag |= 0b10000;
		}

		CharType[] cts = CharType.values();
		for (int i = 0, imax = cts.length; i < imax; ++i)
			if ((flag & (1 << i)) != 0) ret.add(cts[i]);

		return ret;
	}

	/** 格式化日期时间为"yyyy-MM-dd HH:mm:ss"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String formatDateTime(Date date) {
		return date != null ? dtfThreadLocal.get().format(date) : "";
	}

	/** 格式化日期时间为"yyyy-MM-dd"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String formatDate(Date date) {
		return date != null ? dfThreadLocal.get().format(date) : "";
	}

	/** 格式化日期时间为"HH:mm:ss"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String formatTime(Date date) {
		return date != null ? tfThreadLocal.get().format(date) : "";
	}

	/** 格式化日期时间为"yyyy-MM-dd'T'HH:mm:ss'Z'"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String formatGmtDateTime(Date date) {
		return date != null ? gfThreadLocal.get().format(date) : "";
	}

	/** 格式化日期时间为"yyyy-MM-dd HH:mm:ss"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String format(Date date) {
		return formatDateTime(date);
	}

	/** 格式化日期时间为"yyyy-MM-dd HH:mm:ss"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String format(LocalDateTime date) {
		return date != null ? date.format(dateTimeFmt.get()) : "";
	}

	/** 格式化日期时间为"yyyy-MM-dd"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String format(LocalDate date) {
		return date != null ? date.format(dateFmt.get()) : "";
	}

	/** 格式化日期时间为"HH:mm:ss"格式
	 * @param date 要格式化的日期对象
	 * @return 格式化后的文本
	 */
	public static String format(LocalTime date) {
		return date != null ? date.format(timeFmt.get()) : "";
	}

	/** 获取去除文件名的路径
	 * @param fullpath 全路径的文件名
	 * @return 路径
	 */
	public static String getFilePath(String fullpath) {
		if (fullpath == null || fullpath.isEmpty()) return "";
		int p = fullpath.lastIndexOf('/');
		int p2 = fullpath.lastIndexOf('\\');
		if (p < p2) p = p2;
		return p <= 0 ? "" : fullpath.substring(0, p + 1);
	}

	/** 获取去除路径的纯文件名
	 * @param fullpath 全路径的文件名
	 * @return 纯文件名
	 */
	public static String getFileName(String fullpath) {
		if (fullpath == null || fullpath.isEmpty()) return "";
		int p = fullpath.lastIndexOf('/');
		int p2 = fullpath.lastIndexOf('\\');
		if (p < p2) p = p2;
		return p == -1 ? fullpath : p == fullpath.length() - 1 ? "" : fullpath.substring(p + 1);
	}

	/** 获取去除路径和扩展名的纯文件名
	 * @param fullpath 全路径的文件名
	 * @return 纯文件名
	 */
	public static String getFileNameNoExt(String fullpath) {
		fullpath = getFileName(fullpath);
		int p = fullpath.lastIndexOf('.');
		return p == -1 ? fullpath : fullpath.substring(0, p);
	}

	/** 获取去除扩展名的纯文件名
	 * @param filename 文件名
	 * @return 纯文件名
	 */
	public static String getFileBaseName(String filename) {
		int p = filename.lastIndexOf('.');
		return p == -1 ? filename : filename.substring(0, p);
	}

	/** 获取文件扩展名
	 * @param filename 文件名
	 * @param include 是否包含字符'.'
	 * @return 扩展名，没有返回空字符串
	 */
	public static String getFileExt(String filename, boolean include) {
		if (filename == null) return "";
		int pos = filename.lastIndexOf('.');
		return pos == -1 || pos == filename.length() - 1 ? "" : filename.substring(include ? pos : pos + 1);
	}

	/** 获取文件扩展名
	 * @param filename 文件名
	 * @return 不带"."的扩展名，没有返回空字符串
	 */
	public static String getFileExt(String filename) {
		return getFileExt(filename, false);
	}

	/** 改变文件扩展名
	 * @param filename 文件名
	 * @param newExt 新的扩展名
	 * @return 转换扩展名后的文件名
	 */
	public static String convertFileExt(String filename, String newExt) {
		if (filename == null || filename.isEmpty()) return null;
		int flen = filename.length();
		StringBuilder sb = new StringBuilder(flen + 8);
		int p = filename.lastIndexOf('.');
		if (p == -1) p = flen;
		sb.append(filename, 0, p);
		if (newExt != null && !newExt.isEmpty()) {
			if (newExt.charAt(0) != '.')
				sb.append('.');
			sb.append(newExt);
		}
		return sb.toString();
	}

	public interface SplitLongFunc {
		boolean accept (long value, int index);
	}

	/** 解析由非数字字符分隔的数字字符串
	 * @param value 要解析的字符串
	 * @param useSymbol true: 解析"-"号为负数, false: "-"号作为分隔符进行解析
	 * @param func 回调函数
	 */
	public static void splitLong(String value, boolean useSymbol, SplitLongFunc func) {
		if (value == null || value.isEmpty()) return;
		boolean finded_num = false, finded_sym = false;
		int idx = 0;
		long num = 0;
		for (int i = 0, len = value.length(); i < len; ++i) {
			char c = value.charAt(i);
			if (c >= '0' && c <= '9') {
				finded_num = true;
				//num * 10 = num * 8 + num * 2 = num << 3 + num << 1
				num = (num << 3) + (num << 1) + (c - 48);
			} else if (c == '-' && useSymbol) {
				finded_sym = true;
			} else {
				if (finded_num) {
					finded_num = false;
					if (finded_sym) num = -num;
					// 回调函数返回false，则退出
					if (!func.accept(num, idx++))
						return;
					num = 0;
				}
				finded_sym = false;
			}
		}
		if (finded_num) {
			if (finded_sym) num = -num;
			func.accept(num, idx);
		}
	}

	/** 解析由逗号或空格分隔的数字字符串成整数数组, "-"号解析成负数
	 * @param value 要解析的字符串
	 * @return 列表
	 */
	public static List<Long> splitLong(String value) {
		return splitLong(value, true);
	}

	/** 解析由逗号或空格分隔的数字字符串成整数数组
	 * @param value 要解析的字符串
	 * @param useSymbol true: 解析"-"号为负数, false: "-"号作为分隔符进行解析
	 * @return 列表
	 */
	public static List<Long> splitLong(String value, boolean useSymbol) {
		ArrayList<Long> ret = new ArrayList<>();
		splitLong(value, useSymbol, (v, i) -> ret.add(v));
		return ret;
	}

	/** 解析由逗号或空格分隔的数字字符串成整数数组, "-"号解析成负数
	 * @param value 要解析的字符串
	 * @return 列表
	 */
	public static List<Integer> splitInt(String value) {
		return splitInt(value, true);
	}

	/** 解析由逗号或空格分隔的数字字符串成整数数组
	 * @param value 要解析的字符串
	 * @param useSymbol true: 解析"-"号为负数, false: "-"号作为分隔符进行解析
	 * @return 列表
	 */
	public static List<Integer> splitInt(String value, boolean useSymbol) {
		ArrayList<Integer> ret = new ArrayList<>();
		splitLong(value, useSymbol, (v, i) -> ret.add((int)v));
		return ret;
	}

	/** 解析日期时间字段成 年/月/日/时/分/秒/毫秒/时区 数组
	 * @param value 要解析的字符串
	 * @return 列表
	 */
	public static int[] splitDate (String value) {
		int[] ret = new int[SPLIT_DATE_CAPACITY];
		splitLong(value, false, (v, i) -> {
			if (i < SPLIT_DATE_CAPACITY) {
				ret[i] = (int) v;
				return true;
			} else {
				return false;
			}
		});
		return ret;
	}

	// 根据日期文本, 计算时区的偏移值(毫秒)
	private static int calcTimeZone(String dateStr, int offset, int[] dateArray) {
		// 起始偏移位置无效, 直接返回数组中毫秒值
		if (offset == -1) return dateArray[6];

		// 尾部没有时区偏移, 直接返回数组中毫秒值
		int plusIndex = dateStr.indexOf('+', offset);
		if (plusIndex == -1 && dateStr.indexOf('-', offset) == -1
				&& dateStr.charAt(dateStr.length() - 1) != 'Z')
			return dateArray[6];

		// 时区的表示形式是小时和分钟偏移合在一起
		if (dateArray[6] >= 100) {
			dateArray[7] = dateArray[6] % 100;
			dateArray[6] = dateArray[6] / 100;
		}

		// 日期文本的时区偏移值
		int date_timezone = dateArray[6] * 3600_000 + dateArray[7] * 60_000;
		// 判断是加时区还是减时区, 减时区要取负
		if (plusIndex == -1) date_timezone = -date_timezone;
		// 把日期文本表示时区转成本地时区的偏移值
		return LOCAL_ZONE_OFFSET - date_timezone;
	}

	/** 解析时间日期格式, yyyy-MM-dd HH:mm:ss.sss格式 或iso8601格式
	 * @param text 要解析的字符串
	 * @return 日期
	 */
	public static Date parseDate(String text) {
		if (text == null || text.isEmpty()) return null;

		// 有效值为 "2019-02-03" "2019-02-03 04:05:06" "2019-02-03 04:05:06.333"
		// "2019-02-03T04:05:06Z" "2019-02-03T04:05:06+08"
		// "2019-02-03T04:05:06+0800" "2019-02-03T04:05:06+08:00"
		// 日期格式错误, 起始分隔符不在4位年份之后
		if (text.length() < 8 || text.indexOf('-') < 4) return null;

		// ISO8601格式错误, "T"所在位置有误
		int t_idx = text.indexOf('T');
		if (t_idx != -1 && t_idx < 8) return null;

		// 解析日期字符串, 并写入cal
		int[] vs = splitDate(text);

		Calendar calendar = calThreadLocal.get();
		//noinspection MagicConstant
		calendar.set(vs[0], vs[1] - 1, vs[2], vs[3], vs[4], vs[5]);
		calendar.set(Calendar.MILLISECOND, 0);
		int tz_offset = calcTimeZone(text, t_idx, vs);
		calendar.add(Calendar.MILLISECOND, tz_offset);
		return calendar.getTime();
	}

	/** 解析本地日期格式 yyyy-MM-dd格式
	 * @param text 要解析的字符串
	 * @return 结果
	 */
	public static LocalDate parseLocalDate(String text) {
		// 日期格式错误
		if (text == null || text.length() < 5 || text.indexOf('-') < 1)
			return null;
		int[] vs = splitDate(text);
		return LocalDate.of(vs[0], vs[1], vs[2]);
	}

	/** 解析本地日期时间格式 yyyy-MM-dd HH:mm:ss.sss 格式
	 * @param text 要解析的字符串
	 * @return LocalDateTime
	 */
	public static LocalDateTime parseLocalDateTime(String text) {
		// 日期格式错误
		if (text == null || text.length() < 8 || text.indexOf('-') < 4)
			return null;

		// ISO8601格式错误, "T"所在位置有误
		int t_idx = text.indexOf('T');
		if (t_idx != -1 && t_idx < 8) return null;

		int[] vs = splitDate(text);
		LocalDateTime ldt = LocalDateTime.of(vs[0], vs[1], vs[2], vs[3], vs[4], vs[5]);
		long tz_offset = calcTimeZone(text, t_idx, vs);
		return ldt.plusNanos(tz_offset * 1000_000);
	}

	/** 解析本地时间格式 HH:mm:ss.sss格式
	 * @param text 要解析的字符串
	 * @return LocalTime
	 */
	public static LocalTime parseLocalTime(String text) {
		if (text == null || text.isEmpty()) return null;
		if (text.indexOf(':') < 1 || text.length() < 5) return null;
		if (text.indexOf('T') != -1 || text.indexOf(' ') != -1)
			return parseLocalDateTime(text).toLocalTime();
		int[] vs = splitDate(text);
		return LocalTime.of(vs[0], vs[1], vs[2]);
	}

	/** 解析命令行参数，双引号""表示一个完整的参数，反斜杠\表示转义字符
	 * @param line 命令行
	 * @return 解析后的参数
	 */
	public static List<String> parseCmdLine(CharSequence line) {
		ArrayList<String> args = new ArrayList<>();
		if (line == null || line.length() == 0) return args;

		StringBuilder sb = new StringBuilder();
		boolean inWord = false, isQuota = false;
		// 处理用户输入的命令, 分割成标准的命令行参数
		for (int pos = 0, len = line.length(); pos < len; pos++) {
			char c = line.charAt(pos);
			// 上一个是转义字符, 直接写入本字符
			switch (c) {
				// 双引号内的空格不做处理, 否则生成一个命令或参数
				case ' ':
					if (inWord) {
						if (isQuota) sb.append(c);
						else {
							args.add(sb.toString());
							sb.delete(0, sb.length());
							inWord = false;
						}
					}
					break;
				// 起始双引号做标记, 结束双引号生成命令或参数
				case '"':
					if (inWord) {
						if (isQuota) {
							args.add(sb.toString());
							sb.delete(0, sb.length());
							isQuota = false;
							inWord = false;
						}
						else sb.append(c);
					}
					else {
						inWord = true;
						isQuota = true;
					}
					break;
				// 转义字符, 标记
				case '\\':
					if (pos + 1 < len && line.charAt(pos + 1) == '"') {
						sb.append('"');
						++pos;
					}
					else sb.append(c);
					break;

				default:
					sb.append(c);
					inWord = true;
					break;
			}
		}
		if (sb.length() > 0) args.add(sb.toString());

		return args;
	}

	@FunctionalInterface
	public interface SplitFunc {
		boolean accept(int start, int stop, int index);
	}

	/** 解析字符串,按指定的字符做分隔符
	 * @param text 要解析的文本
	 * @param separator 分隔符字符串, 每个字符均起到分隔作用
	 * @return 解析后的字符串列表
	 */
	public static List<String> split(CharSequence text, String separator) {
		ArrayList<String> ret = new ArrayList<>();
		split(text, separator, (b, e, i) -> {
			ret.add(text.subSequence(b, e).toString());
			return true;
		});
		return ret;
	}

	/** 解析字符串,按指定的字符做分隔符
	 * @param text 要解析的文本
	 * @param separators 分隔符字符串, 每个字符均起到分隔作用
	 * @param onSplit 回调处理接口
	 */
	public static void split(CharSequence text, String separators, SplitFunc onSplit) {
		if (text == null || text.length() == 0)
			return;
		int textLen = text.length();
		if (separators == null || separators.isEmpty()) {
			onSplit.accept(0, textLen, 0);
			return;
		}

		char[] ss = separators.toCharArray();
		int slen = separators.length(), index = 0, pos = -1;
		char sFirst = ss[0], sTwo = slen > 1 ? ss[1] : 0, sThree = slen > 2 ? ss[2] : 0;
		for (int i = 0; i < textLen; ++i) {
			char c = text.charAt(i);
			boolean finded;
			// 针对4个以内的分隔数组进行优化处理
			switch (slen) {
				case 1: finded = c == sFirst; break;
				case 2: finded = c == sFirst || c == sTwo; break;
				case 3: finded = c == sFirst || c == sTwo || c == sThree; break;
				default:
					finded = false;
					for (int j = 0; j < slen; ++j)
						if (c == ss[j]) {
							finded = true;
							break;
						}
			}
			if (finded) {
				if (index < i) {
					// 回调函数返回false，表示不再继续
					if (!onSplit.accept(index, i, ++pos))
						return;
				}
				index = i + 1;
			}
		}
		// 处理最后一个被分割的字符串
		if (index < textLen) onSplit.accept(index, text.length(), ++pos);
	}

	/** 转换文本内容为对象
	 * @param cls 对象类型
	 * @param value 文本内容
	 * @return 转换后生成的对象，转换失败返回null
	 */
	@SuppressWarnings("unchecked")
	public static <T> T valueOf(String value, Class<T> cls) {
		if (value == null || value.isEmpty()) return null;

		Object ret = null;
		if (cls == String.class) ret = value;
		else if (cls == Integer.class) ret = Integer.valueOf(value);
		else if (cls == Long.class) ret = Long.valueOf(value);
		else if (cls == Boolean.class) ret = Boolean.valueOf(value);
		else if (cls == Float.class) ret = Float.valueOf(value);
		else if (cls == Double.class) ret = Double.valueOf(value);
		else if (cls == Date.class) ret = parseDate(value);
		else if (cls == LocalDateTime.class) ret = parseLocalTime(value);
		else if (cls == LocalDate.class) ret = parseLocalDate(value);
		else if (cls == byte[].class) ret = fromHex(value, null);
		else if (cls == LocalTime.class) ret = parseLocalTime(value);
		else if (cls == BigInteger.class) ret = new BigInteger(value);
		else if (cls == BigDecimal.class) ret = new BigDecimal(value);
		else if (cls == Short.class) ret = Short.valueOf(value);
		else if (cls == Byte.class) ret = Byte.valueOf(value);
		else if (cls == Character.class) ret = value.charAt(0);
		else if (cls.isEnum()) {
			for (T v : cls.getEnumConstants()) {
				if (value.equals(v.toString())) {
					ret = v;
					break;
				}
			}
		} else {
			try {
				Method m = cls.getMethod("valueOf", String.class);
				if (m.getReturnType() == cls) {
					int mod = cls.getModifiers();
					if (Modifier.isStatic(mod) && Modifier.isPublic(mod))
						ret = m.invoke(null, value);
				}
			} catch (Exception ignored) { }
			
		}

		return (T) ret;
	}

	/** 获取新的uuid(没有"-"号)
	 * @param uuid UUID变量
	 * @return uuid值
	 */
	public static String uuid(UUID uuid) {
		long v1 = uuid.getMostSignificantBits();
		long v2 = uuid.getLeastSignificantBits();
		char[] buf = new char[32];
		int bpos = 0;
		for (int i = 64 - 4; i >= 0; i -= 4)
			buf[bpos++] = (HEX.charAt((int)(v1 >> i) & 0xf));
		for (int i = 64 - 4; i >= 0; i -= 4)
			buf[bpos++] = (HEX.charAt((int)(v2 >> i) & 0xf));
		return new String(buf);
	}

	/** 获取新的uuid
	 * @return uuid值
	 */
	public static String uuid() {
		return uuid(UUID.randomUUID());
	}

}
